package com.jt.aop;

import com.jt.annotation.CacheFind;
import com.jt.util.ObjectMapperUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.util.Arrays;

@Aspect //标识是一个切面
@Component //交给spring管理
public class RedisCache {
    @Autowired
    //private Jedis jedis;  //redis单台测试
    //private ShardedJedis jedis; //redis分片操作
    //private JedisSentinelPool sentinelPool;  //注入池对象
     private JedisCluster jedis;//redis集群


//    @Pointcut("@annotation(com.jt.annotation.CacheFind)")
//    public void pointCut(){}

//    @Around("pointCut()")
// 如果没有别的业务要使用切入点表达则可以不写，直接写在注解上就好

    /**
     * 实现思路：
     *      1、动态获取key  用户定义的前缀+用户参数
     *      2、判断key是否存在
     *          存在 直接从redis中获取数据json 需要将json转化为具体的对象，需要获取目标方法的返回值类型
     *          不存在 执行目标方法，获取返回值数据，将返回值数据转化为json格式，之后保存到缓存中
     * @param joinPoint
     * @return
     */
    @Around("@annotation(cacheFind)")
    public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){
        //Jedis jedis = sentinelPool.getResource();
        //返回结果
        Object data = null;
        //1、获取key
        String key = cacheFind.key();
        //2、获取参数
        String  args= Arrays.toString(joinPoint.getArgs());
        //3、拼接key  前缀+用户参数
         key = key + "::" + args;
        System.out.println(key);
        try {

            if(jedis.exists(key)) {
                //如果数据存在,需要从redis中获取json数据,之后直接返回
                String json = jedis.get(key);
                //1.获取方法对象,   基于方法对象获取方法的返回值类型
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                //2.获取返回值类型
                Class returnType = methodSignature.getReturnType();
                //3、把json按方法的返回值类型进行转化为对象
                data = ObjectMapperUtil.toObject(json, returnType);

            }else{
                //缓存中没有数据，执行目标方法
                data=joinPoint.proceed();
                //1、把返回的数据转化为json字符串
                String json = ObjectMapperUtil.toJson(data);
                //2、判断数据中的超时时间
                if(cacheFind.seconds()>0){
                    //保存数据的同时设置超时时间
                    jedis.setex(key,cacheFind.seconds(),json);
                }else{
                    jedis.set(key,json);
                }
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw new RuntimeException(throwable);
        }
         //jedis.close();  //还池操作
        return data;
    }

    /**
     *  切入点表达式
     *      baen:bean表达式一般应用于类级别，实现粗粒度的切入点定义
     *          bean("userServiceImpl")指定一个userServiceImpl类中所有方法。
     *
     *      Within:within表达式应用于类级别，实现粗粒度的切入点表达式定义
     *          within("aop.service..*") 指定当前目录以及子目录中类的所有方法。
     *
     *     execution:execution表达式应用于方法级别，实现细粒度的切入点表达式定义
     *             语法：execution(返回值类型 包名.类名.方法名(参数列表))。 .为一级包下的类 ..为所有子包下的类
     *          execution(* aop.service..*.*(..)) 万能配置 匹配所有aop.service下所有的类里边的所有方法
     *
     *      @annotation：@annotaion表达式应用于方法级别，实现细粒度的切入点表达式定义
     *          @annotation(anno.RequiredLog) 匹配有此注解描述的方法。
     */


    /**
     * 通知方法
     *      before   目标方法执行前执行
     *      afterReturning  目标方法正确执行后执行
     *      afterThrowing    目标方法执行后抛出异常时执行
     *      after 目标方法执行后执行，不管对错都执行
     *      上述通知类型一般用来记录程序运行状态
     *      around  目标方法执行前后都要执行
     *
     *  连接点
     *      JoinPoint 一般适用于前四大通知类型
     *      ProceedingJoinPoint 用于环绕通知
     */
/*
    @Before("pointCut()")
    public void before(JoinPoint jp){
        //获取目标对象
        Object target = jp.getTarget();
        //target==com.jt.service.impl.ItemCatServiceImpl@4631785a
        System.out.println("target=="+target);


        //className==com.jt.service.impl.ItemCatServiceImpl
        String className = jp.getSignature().getDeclaringTypeName();

        //singnature==List com.jt.service.impl.ItemCatServiceImpl.findItemCatCache(Long)
        Signature signature = jp.getSignature();

        //methodName==findItemCatCache
        String methodName = jp.getSignature().getName();
    }
*/

}